fix: upgrade ip-address to 10.3.1 (CVE-2026-69192) - #2695
Open
anupamme wants to merge 1 commit into
Open
Conversation
Automated dependency upgrade by OrbisAI Security
|
Merging to
After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here |
garrytan
added a commit
that referenced
this pull request
Sep 1, 2026
…n from 105, 18 community PRs absorbed, upgrade path can't eat installs (#2752) * fix(auq): spawned trigger is objective — explicit declaration or STATUS echo, never inference (periodic-lane AUQ collapse) The v1.76 spawned rule's parenthetical '(or your dispatch prompt marks this session as spawned)' let the model INFER spawned status from a scripted-looking prompt in a CI-looking session and silently auto-choose every review-phase question: reviewCount=0 across the plan-review periodic E2Es (weekly run 33363624506, 9 of 14 failed shards; reproduced locally, zero AUQ fingerprints). Env and hook paths were excluded by inspection: hermetic children echo SESSION_KIND: interactive (CLAUDE_CODE_ENTRYPOINT=cli beats CI markers) and the question-preference hook isn't installed there. The trigger is now objective: the echoed SESSION_KIND: spawned STATUS line, or an EXPLICIT dispatch-prompt declaration ("you are a SPAWNED subagent") — declared, never inferred — with an absence-safe interactive fence: CI env vars, scripted-looking or pasted prompts, and write-to-this-exact-file instructions are NOT spawned markers. The prose channel stays because Task-tool subagents inherit the parent env (no spawned prefix) — their dispatch prompt is the only signal; #2733's env-prefix channel is untouched. 19 carve skeleton ceilings re-pinned with measured values (+~440 bytes/skill); ship goldens refreshed for all three hosts; resolver pins extended with the no-inference regression tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: mktemp failure aborts loudly at all three skill-content sites; failed upgrade swap restores the backup (#2679) An empty $(mktemp) result silently disabled the redaction pass (redact-doc resolver, ship pr-body) and made /gstack-upgrade's vendored path destructive: clone lands at "/gstack", the swap mv fails, and rm -rf then deletes BOTH the live install's backup and "". All three sites now guard the assignment with a loud exit; the vendored block additionally restores the backup when the swap fails (same failure class — backup deletion after a failed mv) and the GitLab MR path sends the SCANNED file's bytes instead of re-rendering an unscanned heredoc. bin/gstack-redact rejects an explicit empty --from-file path instead of silently falling through to stdin. Receipts: 6 of 8 new regression checks fail on a v1.77.0.0 scratch worktree. Fixes #2679 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(auq): the interactive fence classifies the session — it never nudges ask-count Burn-in run 1 of the periodic repro overshot the review band (reviewCount=8 > CEILING=7) with the fence's 'when unsure, ask' tail: that phrasing is a quota nudge, not a classification default. The fence now states it only classifies the session and never changes how many questions the skill asks. Pin added. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ci): OSV suppression config actually loads — explicit global --config + expiring, reasoned ignores The ignore file was inert from v1.65.0.0: OSV-Scanner only auto-discovers configs named osv-scanner.toml (no leading dot) and applies them per-directory, so the root config never covered lib/diagram-render/bun.lock either way. The workflow now passes --config=.osv-scanner.toml globally. Every IgnoredVulns entry carries a reason with an upgrade trigger and an ignoreUntil expiry (~90 days) so suppressions must be re-justified. A wiring test pins flag ↔ filename ↔ entry hygiene so the file can never silently go inert again. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(deps): dependency wave — 105 OSV advisories → 3 reasoned suppressions, all lanes verified on the pinned scanner Root: overrides pin ip-address 10.3.1 (defeats BOTH nested nodes — socks' range pull and express-rate-limit's exact 10.1.0 pin, which a top-level bump provably cannot reach) and sharp 0.35.0 (GHSA-f88m, HIGH; transformers still pins ^0.34 upstream — smoke-tested round-trip); marked ^18.0.11; full in-range lockfile refresh clears hono, fast-uri, protobufjs, qs, body-parser, nanoid, uuid, immutable and friends. lib/diagram-render (via its own build-script contract: exact pins edited, fresh lock, dist rebuilt): mermaid 11.16.1, @excalidraw/excalidraw 0.18.1, @excalidraw/mermaid-to-excalidraw 1.1.2 → 2.2.2 — the 1.x line exact-pinned mermaid 10.9.x and dragged the entire duplicate mermaid-10 advisory chain (dompurify 3.1.6, nanoid 3.3.3, lodash-es); the bundle shrinks 9.96 → 7.59 MB with the duplicate mermaid gone. Nested exact pins that survived get scoped overrides (nanoid 5.1.16, lodash-es 4.18.1). Verification: clean-worktree frozen-lockfile installs (root + nested) + the SAME osv-scanner release the action pins (v2.3.8) with the workflow's exact scan-args → exit 0, 'No issues found'. Smoke tests cover the override surfaces (sharp round-trip, ip-address lockfile assertion, marked parse); socks + diagram-drift suites already pin the rest. Supersedes #2695 (its own lockfile kept socks/ip-address@10.2.0; @anupamme's report credited for the parallel diagnosis). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(gbrain-sync): stub pgrep so the pin case is hermetic The only non-dry-run --code-only child hits #1734's PATH-resolved autopilot probe. A live host daemon is a correct refuse; the test cannot inject processRunning. Neutralize pgrep in the fixture bindir instead of adding a production env hatch. Co-authored-by: Cursor <cursoragent@cursor.com> * test(gbrain-sync): blank inherited GBRAIN_HOME in the pin child Lock paths are checked before pgrep. Spreading process.env let a runner GBRAIN_HOME with a live lock refuse the case before the stub ran. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: point ship design-checklist at installed gstack/review path The /ship Design Review step skipped the checklist because the generated path omitted the gstack/ install segment. Sync the generated skill doc and pin a regression assertion. Co-authored-by: Cursor <cursoragent@cursor.com> Wave-amended: goldens regenerated against the wave tree (author's golden commit 8e7a03ca superseded) * fix(codex): a CLI that cannot execute no longer reports CODEX_MODE: ready Follow-up to #2477. The model probe it added does a real round trip, but its final branch is the `else` of a "model 400" grep, so it swallowed spawn ENOENT, non-executable binaries and missing vendor payloads alongside genuine network timeouts. All three are deterministic — retrying never helps — yet they landed in the fail-open bucket and resolved to `ready`, so every Codex pass was skipped in silence and the review reported itself complete. Observed live: @openai/codex was on PATH with an empty vendor/aarch64-apple-darwin/codex/ directory. gstack said `ready` for two months while no Codex pass ran. Three changes: - `_gstack_codex_model_probe` classifies deterministic install failures (exit 126/127, or stderr matching ENOENT/ENOEXEC/EACCES/"cannot execute binary file") as MODEL_UNUSABLE_INSTALL, exit 2, never cached — a reinstall is picked up on the next probe. Exit 124 and genuine transients still fail open, which is what #2477 intended. - The preflight chain captures the probe's code instead of testing it for truthiness, so exit 2 routes to a new `broken_install` mode whose remedy is `npm install -g @openai/codex` rather than "check your model pin". A missing binary and an unusable model are different problems with different fixes. - `_gstack_codex_version_check` no longer reads a broken CLI as healthy. It ran `codex --version 2>/dev/null | head -1`, which captures head's status, not codex's — and 2>/dev/null discarded the one diagnostic available. It now captures the real exit code and warns on non-zero. Empty-but-successful output stays silent, per the existing "empty output → OK" case. Tests: 6 added to test/codex-hardening.test.ts covering both broken-install shapes, the exit-2 contract, no caching, the transient still failing open, the model 400 still classifying as MODEL_UNUSABLE, and the version-check warning. 845 pass / 0 fail across all 8 suites touching the changed files. Closes #2742 Wave-amended: autoplan hand-maintained preflight chain completed (tmpl+render); install-signature grep gated on failed spawn only; goldens regenerated against the wave tree (author's golden commit 5797d326 superseded); +2 tests * feat(redact): add Groq, Tavily and Notion API key patterns * fix(redact): stop reporting .env.local as an internal hostname `internal.hostname` ends in `.local|.prod|.staging|…`, so `.env.local` matches on `env.local` and a dotenv FILENAME is reported as a leaked internal host. The collision is not exotic. It fires on `--env-file=.env.local` in an npm script, `.env.staging` in a README, `.env.prod` in a .gitignore — ordinary lines on branches that leak nothing. Measured on one private repo, three of four MEDIUM findings in a routine push were this, and the fourth was a deleted localhost URL. That ratio is the real cost: a scanner that reports package.json is one people learn to skim, and skimming is how the HIGH finding it exists for gets missed. The guard follows the `insideUuid` precedent and stays deliberately narrow — it exempts only a span beginning `env.` immediately preceded by a dot, i.e. the literal `.env.<suffix>` form. `api.corp.local`, `build-7.internal` and `myenv.local` all still report. The test pins both directions, and the negative controls are the point: an exemption written as "any span ending .local" would pass the dotenv half while quietly gutting the pattern for every real host. Verified red/green — with the validate hook removed, exactly the 6 dotenv cases fail and all 9 real-host controls still pass. * fix: don't flag git SSH remotes as pii.email `pii.email` matches the `git@github.com` inside `git@github.com:acme/widgets.git`. That is a transport user@host, not a person's address, so any diff touching a clone URL -- a deploy config's repo URL, a submodule entry, a README clone line -- draws a spurious MEDIUM from the pre-push hook. Suppressed by URL shape rather than by adding `git` to EMAIL_ALLOW_LOCALPARTS. A bare `git@` allowlist entry would also suppress a genuine address at a domain that merely begins with "git" (git@gitmail.com), converting a false positive into a false negative -- the worse failure for a guardrail. Two shapes are accepted: - `<user>@<host>:<path>.git` for ANY host, covering self-hosted remotes, plus the equivalent ssh:// URL form. - `git@<known-host>` for github.com, gitlab.com, bitbucket.org and ssh.dev.azure.com, whose bare form appears in docs and in `ssh -T git@github.com` connectivity checks with no path at all. Matched exactly, so gitmail.com is unaffected. emailAllowed now receives the normalized text and the span offset so it can see that surrounding shape; it had only ever been passed the matched span. Tests pin both directions: the SSH remotes go quiet, and a real address still fires -- including at a git host (alex@github.com) and at a git-prefixed domain (git@gitmail.com). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(redact): install-prepush-hook refreshes a stale managed hook The marker check returned before the only writer, so once a repo had the hook, no later change to the wrapper could ever reach it. The `printf x` fail-open fix (v1.64.0.0) has still not landed in any repo that received the hook before it, and a wrapper naming a gstack that has since moved stays pointed at a dead path for the same reason. Compare the body against what this version generates: rewrite on drift, stay a no-op when identical. The chained pre-push.local is untouched on both paths. The existing trailing-newline regression test cannot catch this — it installs into a repo with no prior managed hook, the one case that was never broken. * fix(redact): install-prepush-hook refreshes a stale managed hook The marker check returned before the only writer, so once a repo had the hook, no later change to the wrapper could ever reach it. The `printf x` fail-open fix (v1.64.0.0) has still not landed in any repo that received the hook before it, and a wrapper naming a gstack that has since moved stays pointed at a dead path for the same reason. Compare the body against what this version generates: rewrite on drift, stay a no-op when identical. The chained pre-push.local is untouched on both paths. The existing trailing-newline regression test cannot catch this — it installs into a repo with no prior managed hook, the one case that was never broken. Wave-amended: spawnSync timeouts added to the new tests (v1.77 sync-spawn tripwire) * refactor(redact): name the SSH-remote path lookahead constant Wave polish on the #2734 absorption: the 512-char scp-path lookahead window follows the UUID_CONTEXT_CHARS named-constant convention instead of a magic number at the slice site. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(config): reject malformed cross_project_learnings at set A typo was stored with exit 0, so the feature stayed off and the first-run prompt never returned. Reject like codex_reviews; do not coerce. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(gbrain-detect): classify gbrain >= 0.43 held-lock refusal as engine-locked gbrain 0.43+ refuses a held PGLite lock with exit 1 and the message "GBrain's local database is already open through `gbrain serve` (MCP, PID N)" instead of the pre-0.43 exit 124 + "connect timed out" that the #2194 branch matches. The message matches no known pattern, so the classifier falls through to the defensive broken-config default — and Step 1.5 of /setup-gbrain and /sync-gbrain then tell the user to move a perfectly healthy config.json aside and re-init the engine. Reproduced live on gbrain 0.43.0.0, 0.44.0.0 and 0.46.30.0: with a serve holding the lock, gstack-gbrain-detect reports gbrain_local_status=broken-config; after stopping the serve it reports ok with the same untouched config. Match on the stable substring "already open through", mirroring the existing #2194 branch semantics: engine-locked for pglite, broken-db otherwise. Adds a fake-gbrain behavior for the 0.43+ refusal plus two cases (pglite -> engine-locked, postgres -> broken-db). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(memory-helpers): a slow gitleaks probe no longer disables secret scanning `gitleaksAvailable()` cached every failure the same way, so a 2s timeout on `gitleaks version` was recorded as "the binary is absent" for the rest of the process. One busy moment and the whole ingest ran unscanned behind a single stderr line — a fail-open outcome decided by machine load rather than by anything about the machine's setup. The caller only acts on `scanner === "gitleaks"`, so every later file was written with no scan and no second warning. The probe now classifies three outcomes. ENOENT (and a present-but-unusable binary: bad exit, EACCES) stays cached — that is a fact about the box, and re-probing it per file would be waste. A timeout gets one retry on a 10s budget, and if that also expires nothing is cached: the file is reported unscanned, the warning says so in those words, and the next file probes again. Observed under the 7-way sharded free-test runner, where spawning a shell script inside a temp bin dir took longer than the 2s budget. Tests: the retry path, the no-cache-on-timeout path (the second call must re-probe), and the cached-absent path. The fake gitleaks hangs for 30s rather than racing a short sleep against a short budget, and the budgets are chosen so load cannot flip an outcome: 30s where the retry MUST answer, 800ms where the probe MUST expire. An earlier draft used 1s/5s and flaked under the same shard runner this commit is about. The existing probe test pinned `detect` to calls[1], which a retry breaks; it now asserts the order instead of the index. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0111Mq3JGwZDcstn5wYcbhSw * fix(make-pdf): pdftotext version and flavor probe returns unknown on poppler describeBinary reports version="unknown" flavor="unknown" for every poppler install, so logDiagnostics prints nothing useful on the most common implementation. Two independent causes: 1. poppler writes the -v banner to stderr and exits 0. execFileSync returns stdout (empty) and does not throw on a zero exit, so the stderr fallback in the catch block is unreachable. The in-code comment already notes poppler exits 0, but only the throwing path reads stderr. 2. flavor is matched against the version line alone. poppler prints "pdftotext version 26.06.0" on line 1 and names itself on line 2, "Copyright ... The Poppler Developers", so even a working stderr read yields "unknown". Switch the probe to spawnSync, which returns both streams regardless of exit status, match the version banner rather than assuming line 0, and derive the flavor from the full output. Measured on poppler 26.06.0 (Homebrew, macOS), same machine and binary: before: { version: "unknown", flavor: "unknown" } after: { version: "pdftotext version 26.06.0", flavor: "poppler" } xpdf is unaffected: it exits non-zero and names itself on line 1, so it resolved correctly before and still does. Tests use shell shims reproducing each vendor's banner, stream and exit status, since a real pdftotext cannot be assumed present in CI. Two of the four fail on this commit's parent; the xpdf and no-banner cases pass there and are included as regression guards rather than red-proofs. * fix(open-gstack-browser): pre-flight cleanup never killed the stale daemon Step 0 read the old pid with `grep -o '"pid":[0-9]*'` and Step 2 read the port the same way. Neither can match. Every writer of that file in browse/src/server.ts serializes with `JSON.stringify(state, null, 2)`, so the bytes on disk are `"pid": 12060` — colon, space, digits. The failure was silent in the worst way. `_OLD_PID` came back empty, the kill never ran, browse.json was deleted anyway, and the next `connect` died with "existing daemon has different config (proxy/headed mismatch)" — an error pointing at proxy/headed flags rather than at the cleanup that no-opped. Caught against a daemon left over from a reboot: the operator was told to check flags they had never passed. Both patterns now accept optional whitespace. The new tripwire does not match strings — it RUNS the snippets the skill hands the agent, against a state file written exactly the way the server writes one, and asserts pid and port come back out. A third case pins the coupling to `JSON.stringify(state, null, 2)`, so a switch to compact JSON surfaces as a failing expectation rather than as silence. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0111Mq3JGwZDcstn5wYcbhSw * test(make-pdf): clean up the pdftotext shim tmpdir after the suite Wave polish on the #2690 absorption: the describe-scope mkdtemp left one directory per run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(browse): honour CHROMIUM_PROFILE in cli profile-lock cleanup cli.ts resolved the Chromium profile dir with a hardcoded $HOME/.gstack/chromium-profile, while browser-manager launches the profile returned by config.resolveChromiumProfile(), which honours CHROMIUM_PROFILE and GSTACK_HOME. killOrphanChromium() and cleanChromiumProfileLocks() are called with no argument, so whenever CHROMIUM_PROFILE was set they cleaned locks for, and killed Chromium on, the DEFAULT profile rather than the one being launched. Starting a browser with a custom profile therefore evicted an unrelated browser running on the default profile. Delegating to resolveChromiumProfile() also picks up GSTACK_HOME and os.homedir(), so the cleanup path now matches the launch path on Windows where HOME is frequently unset. * fix(auq): the interactive fence is quota-silent — it defers to the skill's own decision points Burn-in calibration: run 1 (fence tail 'when unsure, ask') overshot the plan-ceo review band at reviewCount=8; run 2 (tail mentioning 'HOW MANY questions') undershot at 1. Any ask-count language in the fence anchors the model in one direction or the other. The tail now says only: classify as interactive, then follow the skill's own decision-point instructions exactly as written. Pins updated to forbid count language in either direction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(browse): pin cli.ts profile-dir wiring to the canonical resolver Wave-added coverage for the #2732 absorption: a 6-line fix with zero tests is how the hardcoded path shipped in the first place. resolveChromiumProfile's env behavior is already pinned in config.test.ts; this pins cli.ts's delegation and forbids the hardcoded path from returning. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(browse): preserve return value for async IIFE expressions in js/eval (#2727) Wave-amended: test moved to browse/test/ (browse unit-test convention); trailing-semicolon normalization kept — it is load-bearing for the expression wrapper * fix: bin writers drop data on Windows paths with an apostrophe Two independent Windows git-bash bugs in the bin writers, both silent because callers invoke these scripts with 2>/dev/null and do not check the exit status — a hard failure was indistinguishable from success. Bug 1 — apostrophe in the checkout path breaks the bun -e program. gstack-learnings-log, gstack-question-log and gstack-telemetry-log build a bun -e program as a double-quoted shell string and interpolate SCRIPT_DIR into a single-quoted JS import specifier. A path such as C:/Users/Someone's PC/... closes the JS string literal early and Bun fails to parse ("Expected ; but found s"). Every learning write and every plan-tune question event no-oped; telemetry error redaction fell to its fail-closed null path. The #1950 cygpath -m guard did not cover this — cygpath normalises the drive form but does not remove the apostrophe. Fixed by not interpolating the path at all: cd into the module root and use a relative import specifier, which is immune to apostrophes, spaces, backslashes and MSYS paths alike. The one remaining interpolated data path in gstack-developer-profile (readFileSync of PROFILE_FILE) is passed via the environment instead, matching do_log_session in the same file. Bug 2 — gstack-developer-profile --derive fails on an MSYS-form GSTACK_HOME. GSTACK_HOME defaults to $HOME/.gstack, which under git-bash is /c/Users/..., and Bun on Windows cannot open that form (ENOENT). This script carried no cygpath guard at all. Fixed by normalising GSTACK_HOME once, before PROFILE_FILE / LEGACY_FILE / the events path are derived from it, so all three pick up the normalised value. Adds test/hostile-path-writers.test.ts, which runs the bins from a directory whose name contains an apostrophe and asserts that rows are ACTUALLY WRITTEN (not merely that the exit code is 0 — exit-code-only checks are what masked bug 1). The apostrophe repro is OS-independent: SCRIPT_DIR derives from the script's own location, so a copied checkout under a hostile directory name reproduces bug 1 on Linux/macOS CI too. Wave-amended: all four writers unified on the env-var import pattern the PR already used in gstack-developer-profile (no CWD-dependent module resolution) Wave-amended: all four writers unified on the env-var import pattern the PR already used in gstack-developer-profile (apostrophe-safe without CWD-dependent module resolution); import-shape pin updated * fix(memory-ingest): stop two silent transcript-ingest failures Two independent bugs made transcript pages silently fail to reach the brain. 1. Frontmatter fence gluing. buildTranscriptPage() built the closing "---" with no trailing newline, and session bodies always start with "## ", so the rendered page ended "...---## User". gbrain's frontmatter matcher (/^---\r?\n([\s\S]*?)\r?\n---(\r?\n|$)/ in src/core/markdown.ts) requires the closing "---" to end its own line, so it skipped the glued fence, latched onto the next standalone "---" in the transcript body, parsed the prose between as YAML, and dropped the page with "Invalid YAML frontmatter". Transcripts with no later "---" fell back to body-only, silently losing their frontmatter. Fix: emit the fence on its own line with a blank separator, matching renderPageBody()'s artifact branch. 2. Slug collisions. Two source files can map to one path-derived slug (a session resumed under the same id on one day, or two ids sharing a 12-char prefix). writeStaged() names each file "${slug}.md", so the second overwrote the first; gbrain collected N-1 of N staged files and the reconciliation guard failed the whole batch every run. Fix: disambiguateSlugs() keeps the first occurrence and gives each later collider a stable "-<sha8(source_path)>" suffix (deterministic, and slug + page_slug move together so writeStaged, the failure mapping, and state recording agree). Exports buildTranscriptPage, renderPageBody, and disambiguateSlugs for tests. Adds regression tests for both failures. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Wave-amended: contributor's local-workaround docblock note removed; issue refs retargeted #2653 (closed by its author) -> #2724 (the live 887-staged-to-0-ingested report) * fix: keep feature markers in GStack state * fix: align feature marker seeding with GStack state Wave-amended: seeding relocation re-applied to the composite action (v1.77 moved CI seeding out of the inline workflow steps the original commit edited); wiring tripwire re-pointed accordingly; stale marker comment updated * chore(upgrade): migrate feature-discovery markers to GSTACK_HOME Follow-through on the #2748 absorption: existing installs answered the continuous-checkpoint and model-overlay prompts with markers beside the install; v1.78 reads them from GSTACK_HOME. Copy them once so nobody gets re-prompted. Idempotent, non-fatal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(land-and-deploy): check fork branch in head repo Wave-amended: gh leaves .headRepository.nameWithOwner empty (verified live against gh 2.83) — owner/name now composed from headRepositoryOwner.login + headRepository.name so reconciliation is not a permanent no-op; fork branches get report-not-delete (maintainers lack fork push rights); pins updated * test: spawn timeouts on the #2748 marker tests (v1.77 sync-spawn tripwire) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test: spawn timeouts on absorbed-PR tests (v1.77 sync-spawn tripwire) The absorbed community tests (#2748, #2676, #2714, #2720) were authored before the v1.77 tripwire required a timeout on every sync spawn in the test trees. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(gbrain): a slow --version probe classifies as timeout, never no-cli (#2716) resolveGbrainBin's bare catch collapsed 'gbrain missing' and 'gbrain present but the 2s --version budget expired' into the same null — freshClassify then said no-cli, which the --is-ok whitelist from #1964 does NOT forgive, so a bun-shim install on a loaded POSIX box silently lost every brain-aware block. The probe now returns a discriminated result (cached per-process, same lifetime the old null had) using the same killed/SIGTERM/ETIMEDOUT discrimination the sources-list probe below already uses; timeout routes to the forgiven 'timeout' status. GSTACK_GBRAIN_VERSION_PROBE_TIMEOUT_MS test override added (same precedent as the sources-probe override). Receipt: the slow-but-present sibling test fails on a v1.77.0.0 scratch worktree (classifies no-cli there). Fixes #2716 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(codex): close the consult-mode fence, report turn.failed as a failure, capture exit codes portably (#2671, #2669) Three defects in the codex skill sections: - The resumed-session bash block never closed its fence; every fenced region after it inverted (prose rendered as code, the synthesis-recommendation tail rendered inert). A repo-wide fence-pairing test now scans every generated SKILL.md and sections/*.md with a CommonMark-faithful state machine (an info-string opener inside a fence is literal content — nested template examples in document-generate/make-pdf stay legal; a file ending inside a fence fails). - The JSONL parsers had no turn.failed branch: a turn that STATED its failure was reported as 'possible mid-stream disconnect'. Challenge and consult now print the event's error and run a three-way completeness check (failed-with- reason / silent-disconnect / ok); consult previously had no completeness check at all. - ${PIPESTATUS[0]} is empty under zsh, so hang detection never fired and every clean run printed a spurious '[codex exit ]'. All three capture sites use ${PIPESTATUS[0]:-${pipestatus[1]}}, pinned statically and EXECUTED under real bash and zsh in the new test. Expect a step-change in codex_timeout telemetry — the counter starts firing for zsh users. Receipt: the portability pin fails on a v1.77.0.0 scratch worktree; the fence fix is structural (17 → 18 fence lines, tail no longer inside a block). Fixes #2671 Fixes #2669 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: outside-voice fallback is labeled honestly — same model family, not cross-model (#2735) When Codex is unavailable, the plan-review outside voice falls back to a Claude subagent and the copy sold it as 'cross-model coverage' with 'genuine independence'. Fresh context is real; cross-model validation is not — a user weighing 'both reviewers agree' deserves to know both reviewers share a model family. Six canonical strings fixed at the resolver source (constants.ts not_installed/not_authed, review.ts outside-voice bullet + three dispatch paragraphs); ~10 generated docs and the ship goldens regenerated. Printing the resolved fallback model at dispatch time is descoped as a functional change (follow-up in the wave dispositions). Fixes #2735 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(relink): skill_prefix patches the gbrain render too — the file the host actually serves (#2738) gstack-relink linked SKILL.md from RENDER_DIR when a gbrain render was active but ran gstack-patch-names only on INSTALL_DIR, so the served frontmatter kept the unprefixed name and skill_prefix=true silently no-oped for every brain-aware skill. The render tree (user-owned, untracked) is now patched too; gstack-patch-names is idempotent so repeat relinks never double-prefix. The gen-skill-docs note that pointed users at relink now describes what relink actually covers. Receipt: the new test fails on a v1.77.0.0 scratch worktree (served render keeps 'name: qa'). Fixes #2738 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(render): section refs point at the FINAL render dir, never the tmp swap dir (#2692) gen-skill-docs bakes its --out-dir into rendered CONTENT (rewriteSectionBase), and both swap-in callers (setup, gstack-config gbrain-refresh) render into claude.tmp.<pid> before the #2569 atomic rename — so every rendered skill carried ~9 dead section Read paths that pointed at a directory the swap had just deleted. New --link-root flag names the final serving dir (defaults to --out-dir for direct-render callers: bin/dev-setup, dev-skill.ts, mkdtemp tests — full caller audit in the wave notes); the rewrite now uses a replacement callback so a $-bearing configured path can't expand as $& in a replacement string. The swap logic itself stays byte-identical. Tests pin the generator contract (tmp out-dir files reference the final dir, $-bearing path included) and both callers' wiring. Fixes #2692 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(setup): persistent timeline Stop hook opt-out — timeline_stop_hook config gate (#2677) --no-team is a one-shot teardown, so every later bare ./setup (including the ones /gstack-upgrade runs) re-registered the timeline Stop hook with no way to say 'never'. New gate mirrors the plan_tune_hooks pattern: flag (--timeline-stop-hook/--no-timeline-stop-hook) > env (GSTACK_TIMELINE_STOP_HOOK) > saved config (timeline_stop_hook) > default yes. An explicit flag persists to config so the decision survives upgrades; an explicit 'no' also removes a live registration (reconciliation), so the opt-out works against installs registered by an older setup. --no-team semantics unchanged (NO_TEAM_MODE is never initialized from config). Full gstack-config surface: DEFAULTS entry, header docs, list/defaults enumeration, warn-and-default validation. Fixes #2677 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(browse): tame the macOS headless GPU spin + reap the lock-less headless Chromium on stop (#2709) Two defects in one report. On macOS 26 / Apple Silicon the headless-shell GPU process pegs ~800% CPU indefinitely after real page work and --disable-gpu alone is not enough; the reporter validated that adding --disable-software-rasterizer/--disable-gpu-compositing/--disable-gpu-watchdog drops it to 0.0% with screenshots still working. The flag block is a pure platform-parameterized function (unit-tested on any host), darwin-gated, headless-only (buildGStackLaunchArgs feeds the headed/GBrowser paths where GPU-off is wrong), with a GSTACK_DISABLE_GPU=off escape. Separately: the headless launch has no userDataDir, so it never writes the SingletonLock that killOrphanChromium walks — 'browse stop' reported success while the orphan kept spinning. The daemon now records the launched child's pid + wall-clock start time in the state file (the xvfbPid/xvfbStartTime contract), and stop paths reap a survivor only after verifying BOTH the recorded start time and a Chromium-looking cmdline — a recycled PID, even one running a different legitimate Chromium, is never killed (identity tests include the coreutils-shebang trap that defeats argv0 renames). macOS efficacy is per the reporter's validation; live re-verification on Apple silicon is tracked in TODOS.md. Refs #2709 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(wtree): a failed touch falls through to the HEAD seed instead of reopening the racy window (#2687) The v1.74 racy-git fix carries the real index's mtime onto the temp copy — but its 'touch -r … || true' meant a FAILED touch silently kept the copy's fresh stamp, marking every entry non-racy and reopening the exact same-size- rewrite hole. A failed touch now discards the copy and seeds from read-tree HEAD (slower; every entry re-hashed; fingerprint stays honest). Verification for #2687 itself: the reporter's same-size-rewrite repro run 20 iterations against this tree — 0 misses (the underlying race was fixed by v1.74's b1485d88 with its own regression test; this wave verifies and closes, it does not claim that fix). Receipt: the stubbed-touch test fails on a v1.77.0.0 scratch worktree. Fixes #2687 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test: rewrite gate pin follows the LINK_ROOT rename (#2692) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: v1.78 fix-wave deferrals filed in TODOS.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * v1.78.0.0 release metadata: VERSION, package.json translation, CHANGELOG wave entry, agents digest Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(osv): ignore ledger names its filed tracking issues (#2753, #2754) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(redact): large reports survive the pipe — exitCode instead of process.exit; inert test payload The wave's PR quality gate failed closed: gate-secret-scan.mjs pipes the diff's added lines into gstack-redact and parses the JSON report, but process.exit() discards stdout still buffered in the pipe — this wave's 646-finding report (202 KB) is the first big enough to arrive truncated (~145 KB) at node's collector, so JSON.parse failed and the gate read 'no report' as HIGH. The report and auto-redact body paths now set process.exitCode and let the runtime drain stdout; exit-code contract unchanged (verified 0/2/3 end-to-end). Also: the C1 test's stdin payload no longer uses a provider-prefix credential shape (the gate correctly flagged it; the content was never read on the error path under test). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(memory-ingest): fence regression test survives Windows tmpdirs Wave polish on the #2699 absorption: the hand-built JSONL interpolated the raw tmpdir into a JSON string — on Windows (D:\a\...) that's an invalid escape, the user line was silently dropped, and the body started at '## Assistant' (Windows Free Tests red). JSON.stringify the path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(auq): the interactive fence ends at classification — all behavioral tails removed The pinned-container periodic lane proved the collapse dead (reviewCount 0 → 7/8/5 across the AUQ suite) but flagged the fence's remaining behavioral clause: 'never adds, removes, or batches the skill's decision points' broke the paired-finding control (5 > 4 — it suppressed the batching that fixture expects), and the band overshot its ceiling (8 > 7). Every behavioral tail tried so far skewed counts somewhere ('when unsure, ask' → 8; 'HOW MANY questions' → 1; 'never batches' → paired control red). The fence now ends at 'When unsure, default to interactive.' — classification only, zero behavior words. Pins forbid every tried-and-failed phrasing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(auq): the spawned trigger is the STATUS echo, nothing else — prose channel removed from the eager path Two pinned-container periodic rounds showed that ANY dispatch-prompt declaration channel in rule 1 keeps question counts unstable (round 1, fence with behavioral clause: paired control 5>4, band 8>7; round 2, bare fence: intermittent 0s return, paired control breaks both directions). The stable regime CI was calibrated against had no spawned prose in the eager path at all. Rule 1 now keys on exactly one machine-verifiable thing: the preamble's own SESSION_KIND: spawned STATUS echo. No text from a dispatch prompt, file, or page can flip a session to auto-choose (the strongest anti-injection form). Subagents that missed the env marker are caught at FAILURE time by the AUQ hooks' spawned escape (explicit declaration, never inference) — a channel that never enters an interactive session's eager reasoning. This reverses the wave's earlier explicit-declaration middle ground (and adopts the outside voice's twice-made echo-only argument) on the new evidence. #2733 protected: skill-e2e-docsync-spawned (gate) passes 1/1 on this prose — the ship Step-18 dispatch forces the env prefix, so the echo fires there. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: periodic-lane stabilization residual filed (#2756) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(browse): chromium reap works off-Linux and on every stale-state path readPidCmdline fell back to '' on darwin (no /proc), so the identity gate never matched and reapRecordedChromium was inert on the platform #2709's GPU-spin reap actually targets — it now falls back to ps -o command=. readPidStartTime no longer throws when ps is missing (Windows): a launch must never die to a reap-bookkeeping probe. Three stale-state cleanup paths (dead-daemon stop, startServer stale cleanup, headed-connect) now reap the recorded chromium BEFORE unlinking the state file instead of orphaning it, and the stop-path wait polls (100ms steps, 1s cap) instead of sleeping a fixed 500ms. Wiring pinned: server-state pid/start-time write, all five cli.ts reap call sites, headless-only GPU-flag push. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(browse): chained IIFE + second statement no longer misclassified as one expression isSingleParenOrIifeExpression accepted any tail after the initial group's close as long as trailing chars looked chain-ish, so `(async()=>{await 1})().then(x=>x); console.log('done')` classified as a single expression and the expression wrapper emitted a SyntaxError. The tail is now consumed as a strict member/call/index/optional-chain walk to END of input via a shared string/escape-aware findBalancedClose scanner; anything else (';', operators) demotes to the block wrapper. Negative + positive tests added. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(browse): move headlessGpuArgs below the import block The #2709 helper landed between two import statements; imports now stay contiguous. No behavior change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(codex-probe): timed-out probe (124) keeps its fail-open contract Exit 124 reached the string-signature branch before the timeout fail-open, so a slow probe whose partial output happened to quote 'permission denied' classified as MODEL_UNUSABLE_INSTALL — a deterministic-broken verdict from a transient condition. 124 is now excluded from the signature branch, and the detect/display greps share one hoisted _BROKEN_SIG regex (they had already drifted: display dropped 'not executable'). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(codex): JSONL parser initializes its state vars in both modes challenge-mode initialized turn_completed_count but tested turn_failed via 'in dir()'; consult-mode initialized neither and rebuilt the counter with a dir() conditional per event. Both parsers now init turn_completed_count and turn_failed up front and use plain checks — same semantics, no module-globals introspection. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ship): PR/MR create aborts on a missing or empty scanned body file Both the gh and glab send blocks now guard [ -s "$PR_BODY_FILE" ] and the prose restates that the variable comes from the scan block — bash blocks run in separate shells, and an unset/empty path would previously send an empty body (gh) or cat's error output (glab) instead of the scanned bytes. Codex/factory ship goldens regenerated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(upgrade): abort when a stale .bak already exists at the install path A leftover $INSTALL_DIR.bak from a crashed upgrade would make the mv nest the live install inside it, and the failure-restore arm would 'restore' the stale backup — possibly deleting the only good copy. The upgrade now refuses to start and tells the user to inspect/salvage the backup. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(redact-doc): mktemp-failure message names what it refuses to send 'refusing to send unscanned <noun>' read as if 'unscanned' modified a missing word for sink nouns like 'the spec body'; now 'refusing to send <noun> unscanned'. Generated spec section refreshed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(setup): typo'd timeline-stop-hook value warns instead of persisting --timeline-stop-hook=noo silently normalized to yes AND wrote yes to config — a persisted decision the user never made. Unrecognized values now warn (naming the source), apply the default for this run only, and skip the config write. The opt-out log line names the actual decision source (flag/env/config) and no longer claims a removal that may not have happened. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(memory-helpers): slow-probe warning no longer suppresses the absent warning One shared _gitleaksWarned flag served two different messages: a 'machine under load, retrying next file' warning early in a run permanently silenced the later 'gitleaks not in PATH; secret scanning disabled' warning — the user never learned scanning was off for good. Split into per-message flags. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(lib): shared isExecTimeout helper; export GbrainBinProbe The killed/SIGTERM/ETIMEDOUT discrimination was hand-rolled at three sites (gbrain version probe, engine classifier, gitleaks probe) and free to drift; it now lives once in lib/gbrain-exec.ts. GbrainBinProbe is exported (it's the return type of exported probeGbrainBin) and the cache carries a rationale comment: caching a timeout for process lifetime is deliberate — the memo dedupes the ~3 probes of one short-lived preamble process. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(gen-skill-docs): extract parsePathFlag; fix rewriteSectionBase docstring --out-dir and --link-root shared near-identical inline parsing; one helper now owns it. The rewriteSectionBase docstring said 'no-op when --out-dir is unset' but the gate is the link root (which --link-root can set independently) — it now describes the real behavior. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(memory-ingest): reunite preparePages with its docblock; pin disambiguateSlugs wiring The #2724 disambiguateSlugs block was inserted between preparePages' docblock and the function, orphaning the secret-scanning policy doc onto the wrong symbol. Reordered. A call-site pin now asserts the prepare→stage flow actually invokes disambiguateSlugs, so a refactor can't drop the call while every unit test stays green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(hostile-path): per-run mkdtemp root; telemetry-log redaction coverage The suite used a FIXED tmpdir name, so concurrent runs (sharded runner, sibling worktrees) tore down each other's trees mid-flight — now a per-run mkdtemp root with the apostrophe dir inside. gstack-telemetry-log was the one bin named in the suite header with no test: it now must append a real row under the hostile path with the credential span redacted (<REDACTED-github.pat>) and the rest of the message preserved. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(config): signal-killed spawns map to -1, not exit 0 Both cfg() helpers defaulted a null spawn status to 0 — a child killed by signal would read as success and mask real failures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(upgrade): v1.78.0.0 feature-marker migration suite The only migration without a dedicated test. Covers copy-when-absent (script must mkdir GSTACK_HOME itself), destination-wins (never overwrites), clean no-op, and two-run idempotence — asserting file existence and contents, not just exit codes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(auq): pin the explicit-declaration-only spawned escape sentence SPAWNED_ESCAPE_SENTENCE's tightened wording had no pin: positive pins on the explicit-declaration clause, negative pins on the retired v1.76 loose parentheticals ('e.g. your dispatch prompt says', 'marks this session as spawned'), and a drift guard that both hook directives embed the constant verbatim. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(gbrain): invalid version-probe timeout env falls back to the default GSTACK_GBRAIN_VERSION_PROBE_TIMEOUT_MS set to 'abc', '-1', or '0' must use the default budget — exercised behaviorally through probeGbrainBin with a fresh PATH per case (the memo keys on PATH). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(codex): execute the JSONL parser under real python3; fence scanner tracks opener length The parser's turn.completed/turn.failed/disconnect semantics were pinned by shape only — now the python block is extracted from both RENDERED sections and run against synthetic event streams (tokens line, FAILED + not-a-disconnect, silence -> disconnect warning, SESSION_ID echo), with byte-equivalence safety pins on the bash double-quote extraction. The fence scanner also gains CommonMark opener-length tracking: a 4-backtick fence wrapping a 3-backtick example no longer false-positives, with a self-test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(redact): large report survives a slow piped consumer Pins the >145KB truncation regression (process.exit before the pipe drained): 900 MEDIUM findings -> 259KB JSON report through a sleep-first POSIX consumer that holds the 64KiB kernel buffer full at child exit; asserts complete parseable JSON with matching counts and exit 2, plus an --auto-redact mirror (700 redactions, final sentinel byte arrives). Harness proven red against a copy of the bin with process.exit restored. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(dev-setup): update LINK_ROOT source pin to the parsePathFlag shape Companion to the gen-skill-docs parsePathFlag extraction: the pin still asserts the same invariant (LINK_ROOT defaults to OUT_DIR, so an in-place render stays a byte-exact no-op) against the new expression. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(changelog): fix-batch properties folded into the v1.78.0.0 entry Stale-backup refusal + empty-scanned-body guard on the mktemp bullet, the redact pipe-truncation fix as its own item (a v1.77 bug), and test counts refreshed to the post-fix-batch suite (8,660). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(upgrade): migration test resolves bash through the parent PATH A hardcoded /usr/bin:/bin child PATH breaks spawn('bash') on the Windows curated lane (spawn resolves against the CHILD env's PATH; no bash.exe lives there). Hermeticity is carried by HOME/GSTACK_* overrides, not PATH. Found by the cycle-2 review pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(memory-helpers): per-run cooldown bounds the slow-gitleaks probe cost Retrying a slow probe per FILE (#2715's slow!=absent split) re-paid up to probe+retry (12s default) per file — an 887-file ingest on a loaded box spent hours re-asking the same slow question. After 3 consecutive slow answers the run stops probing and warns once that remaining files go unscanned; the availability cache is still never written, so the next process probes fresh. Slow/absent discrimination is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(memory-ingest): slug assignments persist across runs via the state consult First-occurrence-keeps-bare was walk-order-dependent ACROSS runs: a source that got the suffixed slug once could take the bare slug the next run (its collider aged out or was skipped as unchanged), leaving gbrain holding the same transcript under two slugs — and a NEW collider could claim a bare slug that state shows belongs to an unchanged source, silently overwriting that page. disambiguateSlugs now consults state.sessions: a recorded slug stays owned by its source_path, re-ingested sources keep their slug verbatim, fresh assignments never take another source's slug, and legacy duplicate records (pre-#2724 overwrites) resolve first-owner-wins and self-heal on the next state write. Stateless behavior is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(changelog): D2/D3 properties folded into the absorbed-PR bullets Gitleaks per-run probe cooldown on the #2715 credit; cross-run slug persistence on the #2699/#2724 credit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: update project documentation for v1.78.0.0 README.md: the persistent timeline Stop hook opt-out (#2677) — flag, env var, and config key with resolution order. BROWSER.md: browse stop against a dead daemon now reaps the recorded headless Chromium child, identity-verified (#2709). CLAUDE.md + CONTRIBUTING.md: free-suite test count ~7,000 → ~8,700 (8,660 as of this wave). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: cross-model doc review fixes for v1.78.0.0 CONTRIBUTING.md: the day-to-day example now edits the .tmpl (SKILL.md is generated); the OSV row states the explicit --config load and the reasoned, expiring ignore contract. BROWSER.md: stop row mentions the identity-checked Chromium reap; env table gains CHROMIUM_PROFILE and GSTACK_DISABLE_GPU rows. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(changelog): headline claims what the receipts show "Both red weekly lanes are green again" overclaimed: OSV is verifiably green (pinned scanner, frozen install, branch dispatch), but the periodic lane keeps its pre-wave churn (#2756) — what this wave proves is that the v1.76 regression that silenced plan reviews is dead. Flagged by the cross-model doc review; headline now leads with the user-visible outcome. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: y$un_ <forrest.sun527@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Lockyer <135391289+Lockyer228@users.noreply.github.com> Co-authored-by: Udhdhav kheni <udhavkheni12@gmail.com> Co-authored-by: schienbiz <274676847+schienbiz@users.noreply.github.com> Co-authored-by: David Park <show@davidani.com> Co-authored-by: alopes50 <alex@alexlopes.com> Co-authored-by: Peter van Leeuwen <petervanleeuwen@SB-petervanleeuwen.local> Co-authored-by: Denis Zjukow <denis.zjukow@gmail.com> Co-authored-by: Paul Snyman <5826275+snymanpaul@users.noreply.github.com> Co-authored-by: Adam Badar <badaradam10@gmail.com> Co-authored-by: loulanyue <260355617@qq.com> Co-authored-by: Shreshth Kapoor <shreshth@osiflow.com> Co-authored-by: Ryan Ayers <rayers@dividia.net> Co-authored-by: Simon Altit <simon.altit@gmail.com> Co-authored-by: ptt <1928627998@qq.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Upgrade ip-address from 10.2.0 to 10.3.1 to fix CVE-2026-69192.
Vulnerability
CVE-2026-69192bun.lock(dependency:ip-address)Description: ip-address: ip-address: Inconsistent IP address parsing leads to Server-Side Request Forgery (SSRF) and trust-boundary bypass
Evidence
Scanner confirmation: trivy rule
CVE-2026-69192flagged this pattern.Threat Model Context
This is a Node.js library - vulnerabilities affect downstream consumers who use this package.
Changes
bun.lockpackage.jsonBehavior Preservation
The change is scoped to 2 files on the vulnerable path; it only tightens handling of untrusted input and leaves valid inputs unaffected.
This change addresses a pattern flagged by static analysis. The code path handles user-influenced input and the fix reduces the attack surface against both manual and automated exploitation.
Automated security fix by OrbisAI Security